/**
 * Copyright (C) 2023 maminjie <canpool@163.com>
 * Copyright (C) 2017-2018 Uwe Kindler
 * SPDX-License-Identifier: LGPL-2.1
 **/

#include "QxDockAreaTabBar.h"
#include "QxDockAreaWidget.h"
#include "QxDockManager.h"
#include "QxDockOverlay.h"
#include "QxDockWidget.h"
#include "QxDockWidgetTab.h"
#include "QxDockFloatingContainer.h"
#include "QxDockFloatingDragPreview.h"

#include <QApplication>
#include <QBoxLayout>
#include <QDebug>
#include <QMouseEvent>
#include <QScrollBar>
#include <QtGlobal>

#include <iostream>

QX_BEGIN_NAMESPACE

/**
 * Private data class of DockAreaTabBar class (pimpl)
 */
struct DockAreaTabBarPrivate {
    DockAreaTabBar *_this;
    DockAreaWidget *dockArea;
    QWidget *TabsContainerWidget;
    QBoxLayout *TabsLayout;
    int CurrentIndex = -1;

    /**
     * Private data constructor
     */
    DockAreaTabBarPrivate(DockAreaTabBar *_public);

    /**
     * Update tabs after current index changed or when tabs are removed.
     * The function reassigns the stylesheet to update the tabs
     */
    void updateTabs();

    /**
     * Convenience function to access first tab
     */
    DockWidgetTab *firstTab() const
    {
        return _this->tab(0);
    }

    /**
     * Convenience function to access last tab
     */
    DockWidgetTab *lastTab() const
    {
        return _this->tab(_this->count() - 1);
    }
};
// struct DockAreaTabBarPrivate

DockAreaTabBarPrivate::DockAreaTabBarPrivate(DockAreaTabBar *_public) : _this(_public)
{
}

void DockAreaTabBarPrivate::updateTabs()
{
    // Set active TAB and update all other tabs to be inactive
    for (int i = 0; i < _this->count(); ++i) {
        auto TabWidget = _this->tab(i);
        if (!TabWidget) {
            continue;
        }

        if (i == CurrentIndex) {
            TabWidget->show();
            TabWidget->setActiveTab(true);
            _this->ensureWidgetVisible(TabWidget);
        } else {
            TabWidget->setActiveTab(false);
        }
    }
}

DockAreaTabBar::DockAreaTabBar(DockAreaWidget *parent) : QScrollArea(parent), d(new DockAreaTabBarPrivate(this))
{
    d->dockArea = parent;
    setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
    setFrameStyle(QFrame::NoFrame);
    setWidgetResizable(true);
    setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);

    d->TabsContainerWidget = new QWidget();
    d->TabsContainerWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
    d->TabsContainerWidget->setObjectName("tabsContainerWidget");
    d->TabsLayout = new QBoxLayout(QBoxLayout::LeftToRight);
    d->TabsLayout->setContentsMargins(0, 0, 0, 0);
    d->TabsLayout->setSpacing(0);
    d->TabsLayout->addStretch(1);
    d->TabsContainerWidget->setLayout(d->TabsLayout);
    setWidget(d->TabsContainerWidget);

    setFocusPolicy(Qt::NoFocus);
}

DockAreaTabBar::~DockAreaTabBar()
{
    delete d;
}

void DockAreaTabBar::wheelEvent(QWheelEvent *Event)
{
    Event->accept();
    const int direction = Event->angleDelta().y();
    if (direction < 0) {
        horizontalScrollBar()->setValue(horizontalScrollBar()->value() + 20);
    } else {
        horizontalScrollBar()->setValue(horizontalScrollBar()->value() - 20);
    }
}

void DockAreaTabBar::setCurrentIndex(int index)
{
    if (index == d->CurrentIndex) {
        return;
    }

    if (index < -1 || index > (count() - 1)) {
        qWarning() << Q_FUNC_INFO << "Invalid index" << index;
        return;
    }

    Q_EMIT currentChanging(index);
    d->CurrentIndex = index;
    d->updateTabs();
    updateGeometry();
    Q_EMIT currentChanged(index);
}

int DockAreaTabBar::count() const
{
    // The tab bar contains a stretch item as last item
    return d->TabsLayout->count() - 1;
}

void DockAreaTabBar::insertTab(int Index, DockWidgetTab *Tab)
{
    d->TabsLayout->insertWidget(Index, Tab);
    connect(Tab, SIGNAL(clicked()), this, SLOT(onTabClicked()));
    connect(Tab, SIGNAL(closeRequested()), this, SLOT(onTabCloseRequested()));
    connect(Tab, SIGNAL(closeOtherTabsRequested()), this, SLOT(onCloseOtherTabsRequested()));
    connect(Tab, SIGNAL(moved(QPoint)), this, SLOT(onTabWidgetMoved(QPoint)));
    connect(Tab, SIGNAL(elidedChanged(bool)), this, SIGNAL(elidedChanged(bool)));
    Tab->installEventFilter(this);
    Q_EMIT tabInserted(Index);
    if (Index <= d->CurrentIndex) {
        setCurrentIndex(d->CurrentIndex + 1);
    } else if (d->CurrentIndex == -1) {
        setCurrentIndex(Index);
    }

    updateGeometry();
}

void DockAreaTabBar::removeTab(DockWidgetTab *Tab)
{
    if (!count()) {
        return;
    }
    QX_DOCK_PRINT("DockAreaTabBar::removeTab ");
    int NewCurrentIndex = currentIndex();
    int RemoveIndex = d->TabsLayout->indexOf(Tab);
    if (count() == 1) {
        NewCurrentIndex = -1;
    }
    if (NewCurrentIndex > RemoveIndex) {
        NewCurrentIndex--;
    } else if (NewCurrentIndex == RemoveIndex) {
        NewCurrentIndex = -1;
        // First we walk to the right to search for the next visible tab
        for (int i = (RemoveIndex + 1); i < count(); ++i) {
            if (tab(i)->isVisibleTo(this)) {
                NewCurrentIndex = i - 1;
                break;
            }
        }

        // If there is no visible tab right to this tab then we walk to
        // the left to find a visible tab
        if (NewCurrentIndex < 0) {
            for (int i = (RemoveIndex - 1); i >= 0; --i) {
                if (tab(i)->isVisibleTo(this)) {
                    NewCurrentIndex = i;
                    break;
                }
            }
        }
    }

    Q_EMIT removingTab(RemoveIndex);
    d->TabsLayout->removeWidget(Tab);
    Tab->disconnect(this);
    Tab->removeEventFilter(this);
    QX_DOCK_PRINT("NewCurrentIndex " << NewCurrentIndex);
    if (NewCurrentIndex != d->CurrentIndex) {
        setCurrentIndex(NewCurrentIndex);
    } else {
        d->updateTabs();
    }

    updateGeometry();
}

int DockAreaTabBar::currentIndex() const
{
    return d->CurrentIndex;
}

DockWidgetTab *DockAreaTabBar::currentTab() const
{
    if (d->CurrentIndex < 0) {
        return nullptr;
    } else {
        return qobject_cast<DockWidgetTab *>(d->TabsLayout->itemAt(d->CurrentIndex)->widget());
    }
}

void DockAreaTabBar::onTabClicked()
{
    DockWidgetTab *Tab = qobject_cast<DockWidgetTab *>(sender());
    if (!Tab) {
        return;
    }

    int index = d->TabsLayout->indexOf(Tab);
    if (index < 0) {
        return;
    }
    setCurrentIndex(index);
    Q_EMIT tabBarClicked(index);
}

void DockAreaTabBar::onTabCloseRequested()
{
    DockWidgetTab *Tab = qobject_cast<DockWidgetTab *>(sender());
    int Index = d->TabsLayout->indexOf(Tab);
    closeTab(Index);
}

void DockAreaTabBar::onCloseOtherTabsRequested()
{
    auto Sender = qobject_cast<DockWidgetTab *>(sender());
    for (int i = 0; i < count(); ++i) {
        auto Tab = tab(i);
        if (Tab->isClosable() && !Tab->isHidden() && Tab != Sender) {
            // If the dock widget is deleted with the closeTab() call, its tab
            // it will no longer be in the layout, and thus the index needs to
            // be updated to not skip any tabs
            int Offset = Tab->dockWidget()->features().testFlag(DockWidget::DockWidgetDeleteOnClose) ? 1 : 0;
            closeTab(i);

            // If the the dock widget blocks closing, i.e. if the flag
            // CustomCloseHandling is set, and the dock widget is still open,
            // then we do not need to correct the index
            if (Tab->dockWidget()->isClosed()) {
                i -= Offset;
            }
        }
    }
}

DockWidgetTab *DockAreaTabBar::tab(int Index) const
{
    if (Index >= count() || Index < 0) {
        return nullptr;
    }
    return qobject_cast<DockWidgetTab *>(d->TabsLayout->itemAt(Index)->widget());
}

void DockAreaTabBar::onTabWidgetMoved(const QPoint &GlobalPos)
{
    DockWidgetTab *MovingTab = qobject_cast<DockWidgetTab *>(sender());
    if (!MovingTab) {
        return;
    }

    int fromIndex = d->TabsLayout->indexOf(MovingTab);
    auto MousePos = mapFromGlobal(GlobalPos);
    MousePos.rx() = qMax(d->firstTab()->geometry().left(), MousePos.x());
    MousePos.rx() = qMin(d->lastTab()->geometry().right(), MousePos.x());
    int toIndex = -1;
    // Find tab under mouse
    for (int i = 0; i < count(); ++i) {
        DockWidgetTab *DropTab = tab(i);
        if (DropTab == MovingTab || !DropTab->isVisibleTo(this) || !DropTab->geometry().contains(MousePos)) {
            continue;
        }

        toIndex = d->TabsLayout->indexOf(DropTab);
        if (toIndex == fromIndex) {
            toIndex = -1;
        }
        break;
    }

    if (toIndex > -1) {
        d->TabsLayout->removeWidget(MovingTab);
        d->TabsLayout->insertWidget(toIndex, MovingTab);
        QX_DOCK_PRINT("tabMoved from " << fromIndex << " to " << toIndex);
        Q_EMIT tabMoved(fromIndex, toIndex);
        setCurrentIndex(toIndex);
    } else {
        // Ensure that the moved tab is reset to its start position
        d->TabsLayout->update();
    }
}

void DockAreaTabBar::closeTab(int Index)
{
    if (Index < 0 || Index >= count()) {
        return;
    }

    auto Tab = tab(Index);
    if (Tab->isHidden()) {
        return;
    }
    Q_EMIT tabCloseRequested(Index);
}

bool DockAreaTabBar::eventFilter(QObject *watched, QEvent *event)
{
    bool Result = Super::eventFilter(watched, event);
    DockWidgetTab *Tab = qobject_cast<DockWidgetTab *>(watched);
    if (!Tab) {
        return Result;
    }

    switch (event->type()) {
    case QEvent::Hide:
        Q_EMIT tabClosed(d->TabsLayout->indexOf(Tab));
        updateGeometry();
        break;

    case QEvent::Show:
        Q_EMIT tabOpened(d->TabsLayout->indexOf(Tab));
        updateGeometry();
        break;

    // Setting the text of a tab will cause a LayoutRequest event
    case QEvent::LayoutRequest:
        updateGeometry();
        break;

    default:
        break;
    }

    return Result;
}

bool DockAreaTabBar::isTabOpen(int Index) const
{
    if (Index < 0 || Index >= count()) {
        return false;
    }

    return !tab(Index)->isHidden();
}

QSize DockAreaTabBar::minimumSizeHint() const
{
    QSize Size = sizeHint();
    Size.setWidth(10);
    return Size;
}

QSize DockAreaTabBar::sizeHint() const
{
    return d->TabsContainerWidget->sizeHint();
}

QX_END_NAMESPACE
